package com.example.spider.proxy.core.tester;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.example.spider.proxy.core.ProxyAuthenticator;
import com.example.spider.proxy.core.ProxyInfo;
import com.example.spider.util.SkipHttpsVerifyUtil;
import org.shoulder.core.exception.CommonErrorCodeEnum;
import org.shoulder.core.util.AssertUtils;

import java.io.IOException;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.List;
import java.util.Map;

import static com.example.spider.proxy.core.tester.TestConstant.DEFAULT_TEST_TIME_OUT;

/**
 * 代理有效性测试
 */
public class ProxyTesterUtil {

    /**
     * 检测代理ip是否有效并更新其信息
     */
    public static boolean testProxyInfo(ProxyInfo proxyInfo, TestCallBack testCallBack) {
        AssertUtils.notNull(proxyInfo, CommonErrorCodeEnum.ILLEGAL_PARAM);
        AssertUtils.notNull(testCallBack, CommonErrorCodeEnum.ILLEGAL_PARAM);
        HttpURLConnection httpCon = null;
        int respCode = 0;
        long start = System.currentTimeMillis();
        try {
            Proxy proxy = proxyInfo.toProxy();
            switch (proxy.type()) {
                case DIRECT:
                    return true;
                case SOCKS:
                case HTTP:
                default:
                    String aimUrl = testCallBack.getAimUrl(proxyInfo);
                    httpCon = (HttpURLConnection) new URL(aimUrl)
                            .openConnection(proxy);

                    // 忽略 https 验证
                    if (aimUrl.startsWith("https")) {
                        SkipHttpsVerifyUtil.trustAllCerts(httpCon);
                    }

                    testCallBack.getHeaders().forEach(httpCon::setRequestProperty);
                    Authenticator.setDefault(ProxyAuthenticator.getInstance());
                    if (StrUtil.isNotBlank(proxyInfo.getUsername())) {
                        ProxyAuthenticator.getInstance().addIfAbsent(proxyInfo.getIp(), proxyInfo.getPort(), proxyInfo.getUsername(),
                                proxyInfo.getPassword());
                    }
                    //                    String encoded = new String
                    //                            (Base64.getEncoder().encode("proxypwd:proxypwd".getBytes()));
                    //                    httpCon.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
                    httpCon.setConnectTimeout(DEFAULT_TEST_TIME_OUT);
                    httpCon.setReadTimeout(DEFAULT_TEST_TIME_OUT);
                    respCode = httpCon.getResponseCode();
                    Map<String, List<String>> headers = httpCon.getHeaderFields();
                    String responseStr = IoUtil.readUtf8(httpCon.getInputStream());
                    long end = System.currentTimeMillis();
                    long cost = end - start;
                    return testCallBack.onResponse(proxyInfo, httpCon.getResponseCode(), responseStr, cost);
            }
            // TCP 原理：1. client 发送 SYN；
            // 2. server 响应 syn_ack
            // 3. client 发送 ack，此时握手完毕；
            // client重传syn次数达到上限则 connectException；
            // 若重传未完毕，到了 socketTimeOut。则 socketException
        } catch (SocketException | SocketTimeoutException e) {
            // java.net.SocketException: No route to host: connect
            // java.net.SocketException: Address is invalid on local machine, or port is not valid on remote machine
            // java.net.SocketException: connect: Address is invalid on local machine, or port is not valid on remote machine 这个主要是这个ip是局域网的
            // Connection Timed Out 握手时候 SYN_ACK 没超时，后续tcp报文超时
            // Connection reset 收到 RST 报文，上次连接超时或者断开TCP报文未发送成功
            // Connection refused 代理类型可能是 SOCKS / 对方服务器繁忙随机拒绝
            // SOCKS: Connection not allowed by ruleset
            // SOCKS : authentication failed 需要密码
            // Unexpected end of file from server 大概率协议不一致，比如用了 HTTP 链接 SOCKS 服务器
            // Malformed reply from SOCKS server 可能不是 socks 服务器
            // Reply from SOCKS server has bad version 可能不是socks 服务器
            //System.out.println("may type is error " + proxyInfo.getIp() + " " + proxyInfo.getPort() + " " + proxyInfo.getType());
            return testCallBack.onException(proxyInfo, System.currentTimeMillis() - start, e);
        } catch (IOException e) {//407 需要重新发启，一般是密码不对
            // 403 代理把本ip拉黑 or https 不被支持  401 未认证，500 需要登录 重定向+502未认证
            Throwable cause = e.getCause();
            return testCallBack.onException(proxyInfo, System.currentTimeMillis() - start, e);
        } catch (Exception e) {//407 需要重新发启，一般是密码不对
            return testCallBack.onException(proxyInfo, System.currentTimeMillis() - start, e);
        } finally {
            testCallBack.onFinally();
        }
    }

}
